{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "cell_id": "2044f0e17d3d4fb2bd9e49fb527e2f08",
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 718,
    "execution_start": 1672391369517,
    "source_hash": "e850f112",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集大小： 1000\n",
      "[[ 1.7641  0.4002  0.    ]\n",
      " [ 0.9787  2.2409  0.    ]\n",
      " [ 1.8676 -0.9773  1.    ]\n",
      " [ 0.9501 -0.1514  1.    ]\n",
      " [-0.1032  0.4106  1.    ]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 导入数据集\n",
    "data = np.loadtxt('xor_dataset.csv', delimiter=',')\n",
    "print('数据集大小：', len(data))\n",
    "print(data[:5])\n",
    "\n",
    "# 划分训练集与测试集\n",
    "ratio = 0.8\n",
    "split = int(ratio * len(data))\n",
    "np.random.seed(0)\n",
    "data = np.random.permutation(data)\n",
    "# y的维度调整为(len(data), 1)，与后续模型匹配\n",
    "x_train, y_train = data[:split, :2], data[:split, -1].reshape(-1, 1)\n",
    "x_test, y_test = data[split:, :2], data[split:, -1].reshape(-1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "cell_id": "df47c976d4dc400a84ee56a1a14476cf",
    "collapsed": true,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 3,
    "execution_start": 1672391370237,
    "id": "2092E5DBA67946CF87211FF82357EAAB",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "6d7e226e",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 基类\n",
    "class Layer:\n",
    "    \n",
    "    # 前向传播函数，根据输入x计算该层的输出y\n",
    "    def forward(self, x):\n",
    "        raise NotImplementedError\n",
    "    \n",
    "    # 反向传播函数，输入上一层回传的梯度grad，输出当前层的梯度\n",
    "    def backward(self, grad):\n",
    "        raise NotImplementedError\n",
    "    \n",
    "    # 更新函数，用于更新当前层的参数\n",
    "    def update(self, learning_rate):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "cell_id": "b9ba442183c34ee28692f4457c87121a",
    "collapsed": true,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 2,
    "execution_start": 1672391370246,
    "id": "CEF6ADC4DF9C4D1B85E50F3B33957444",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "fa9c1318",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Linear(Layer):    \n",
    "\n",
    "    def __init__(self, num_in, num_out, use_bias=True):\n",
    "        self.num_in = num_in # 输入维度\n",
    "        self.num_out = num_out # 输出维度\n",
    "        self.use_bias = use_bias # 是否添加偏置\n",
    "\n",
    "        # 参数的初始化非常重要\n",
    "        # 如果把参数默认设置为0，会导致Wx=0，后续计算失去意义\n",
    "        # 我们直接用正态分布来初始化参数\n",
    "        self.W = np.random.normal(loc=0, scale=1.0, size=(num_in, num_out))\n",
    "        if use_bias: # 初始化偏置\n",
    "            self.b = np.zeros((1, num_out))\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # 前向传播y = Wx + b\n",
    "        # x的维度为(batch_size, num_in)\n",
    "        self.x = x\n",
    "        self.y = x @ self.W # y的维度为(batch_size, num_out)\n",
    "        if self.use_bias:\n",
    "            self.y += self.b\n",
    "        return self.y\n",
    "    \n",
    "    def backward(self, grad):\n",
    "        # 反向传播，按照链式法则计算\n",
    "        # grad的维度为(batch_size, num_out)\n",
    "        # 梯度要对batch_size取平均\n",
    "        # grad_W的维度与W相同，为(num_in, num_out)\n",
    "        self.grad_W = self.x.T @ grad / grad.shape[0]\n",
    "        if self.use_bias:\n",
    "            # grad_b的维度与b相同，为(1, num_out)\n",
    "            self.grad_b = np.mean(grad, axis=0, keepdims=True)\n",
    "        # 向前传播的grad维度为(batch_size, num_in)\n",
    "        grad = grad @ self.W.T\n",
    "        return grad\n",
    "    \n",
    "    def update(self, learning_rate):\n",
    "        # 更新参数以完成梯度下降\n",
    "        self.W -= learning_rate * self.grad_W\n",
    "        if self.use_bias:\n",
    "            self.b -= learning_rate * self.grad_b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "cell_id": "4ef809abf5b541af8f8b88f19addd90c",
    "collapsed": true,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 1,
    "execution_start": 1672391370282,
    "id": "D7898DAD73B744FFA0A193645E427AB1",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "569d950",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Identity(Layer):\n",
    "    # 单位函数\n",
    "\n",
    "    def forward(self, x):\n",
    "        return x\n",
    "\n",
    "    def backward(self, grad):\n",
    "        return grad\n",
    "\n",
    "class Sigmoid(Layer):  \n",
    "    # 逻辑斯谛函数\n",
    "\n",
    "    def forward(self, x):\n",
    "        self.x = x\n",
    "        self.y = 1 / (1 + np.exp(-x))\n",
    "        return self.y\n",
    "    \n",
    "    def backward(self, grad):\n",
    "        return grad * self.y * (1 - self.y)\n",
    "\n",
    "class Tanh(Layer):\n",
    "    # tanh函数\n",
    "\n",
    "    def forward(self, x):\n",
    "        self.x = x\n",
    "        self.y = np.tanh(x)\n",
    "        return self.y\n",
    "\n",
    "    def backward(self, grad):\n",
    "        return grad * (1 - self.y ** 2)\n",
    "\n",
    "class ReLU(Layer):\n",
    "    # ReLU函数\n",
    "\n",
    "    def forward(self, x):\n",
    "        self.x = x\n",
    "        self.y = np.maximum(x, 0)\n",
    "        return self.y\n",
    "\n",
    "    def backward(self, grad):\n",
    "        return grad * (self.x >= 0)\n",
    "\n",
    "# 存储所有激活函数和对应名称，方便索引\n",
    "activation_dict = { \n",
    "    'identity': Identity,\n",
    "    'sigmoid': Sigmoid,\n",
    "    'tanh': Tanh,\n",
    "    'relu': ReLU\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "cell_id": "b58160878c2942a9b8919908d466d94e",
    "collapsed": true,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 0,
    "execution_start": 1672391370283,
    "id": "C466619E2A1347B99171BCE4E4376141",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "7a8867a1",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class MLP:\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        layer_sizes, # 包含每层大小的list\n",
    "        use_bias=True, \n",
    "        activation='relu',\n",
    "        out_activation='identity'\n",
    "    ):\n",
    "        self.layers = []\n",
    "        num_in = layer_sizes[0]\n",
    "        for num_out in layer_sizes[1: -1]:\n",
    "            # 添加全连接层\n",
    "            self.layers.append(Linear(num_in, num_out, use_bias)) \n",
    "            # 添加激活函数\n",
    "            self.layers.append(activation_dict[activation]()) \n",
    "            num_in = num_out\n",
    "        # 由于输出需要满足任务的一些要求\n",
    "        # 例如二分类任务需要输出[0,1]之间的概率值\n",
    "        # 因此最后一层通常做特殊处理\n",
    "        self.layers.append(Linear(num_in, layer_sizes[-1], use_bias))\n",
    "        self.layers.append(activation_dict[out_activation]())\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # 前向传播，将输入依次通过每一层\n",
    "        for layer in self.layers:\n",
    "            x = layer.forward(x)\n",
    "        return x\n",
    "    \n",
    "    def backward(self, grad):\n",
    "        # 反向传播，grad为损失函数对输出的梯度\n",
    "        # 将该梯度依次回传，得到每一层参数的梯度\n",
    "        for layer in reversed(self.layers):\n",
    "            grad = layer.backward(grad)\n",
    "            \n",
    "    def update(self, learning_rate):\n",
    "        # 更新每一层的参数\n",
    "        for layer in self.layers:\n",
    "            layer.update(learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "cell_id": "8a062d01af634a67906df76c219c1cb4",
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 2039,
    "execution_start": 1672391370284,
    "id": "45011B03F73E42F289E873B5BED8F362",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "e5a5aba3",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试精度： 0.97\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nJGkH8dRTTy1JKfXLO0cLEbU8Vuv3kSLiHOAcgK5dux649957lzOXJKlAGnpubnVldujQoUyfPj3v\nGJKkHUREvJ53hhakAhhcbXsQ2TWht5BSugW4BWDcuHHJc7Mkqak09NzsNGNJklRlCvCZ0qrGhwKV\nKaU38g4lSVJtWt3IrCRJ2jYRcSdwJNA3IiqAbwHtAVJKNwNTyVYyngWsAT6bT1JJkupnmZUkqSBS\nSqfW83wCvtRMcSRJ2i6WWUlqBTZs2EBFRQXr1q3LO0qr1alTJwYNGkT79u3zjiJJkpqAZVaSWoGK\nigq6d+/O0KFDiahtwVltTUqJpUuXUlFRwbBhw/KOI0mSmoALQElSK7Bu3Tr69Oljkd1GEUGfPn0c\n2ZYkaQdimZWkVsIiu338/UmStGOxzEqS6rV8+XJuvPHGbXrtsccey/Llyxu8/2WXXcb3vve9bTqW\nJEkqDsusJKleWyuzmzZt2uprp06dSq9evcoRS5IkFZhlVpJUr0mTJvHKK68wduxYLrroIh566CGO\nOuooPvWpTzFq1CgATjzxRA488ED23XdfbrnllrdfO3ToUJYsWcJrr73GPvvsw+c+9zn23Xdfjj76\naNauXbvV4z7zzDMceuihjB49mpNOOolly5YBcP311zNy5EhGjx7NhAkTAHj44YcZO3YsY8eOZf/9\n92flypVl+m1IkqSWwNWMJamV+epX4ZlnmvY9x46F666r+/mrrrqK559/nmdKB37ooYd44okneP75\n599eHfi2225jp512Yu3atRx00EGcfPLJ9OnT513v8/LLL3PnnXfyox/9iFNOOYVf//rXTJw4sc7j\nfuYzn+GHP/whRxxxBJdeeinf/va3ue6667jqqqt49dVX6dix49tTmL/3ve8xefJkDjvsMFatWkWn\nTp2287ciSZJaskKOzC5bBlOnwpIleSeRpNbr4IMPftdlbq6//nrGjBnDoYceyty5c3n55Ze3eM2w\nYcMYO3YsAAceeCCvvfZane9fWVnJ8uXLOeKIIwA4/fTTeeSRRwAYPXo0p512Grfffjvt2mWfyx52\n2GFceOGFXH/99SxfvvztxyVJ0o6pkGf6F16A446DadPg6KPzTiNJjbO1EdTm1LVr17fvP/TQQzzw\nwAP8/e9/p0uXLhx55JG1XganY8eOb99v27ZtvdOM63LffffxyCOPMGXKFK644gpmzJjBpEmTOO64\n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\nsH17HODu3euB6bZtXtArVvj2CSf4+TVr4kRayR56yBNkzZ3rrcBt2/o443btfPtXv/JgeNEimDSp\n6rl27bw1uVcv/wdfvLjquXbtmtlfKaTR6I8hIiKSpkadZzaEMB2YXu3YbUnbc/HuxyKSww4+2KcS\nSmVIfAje1XnzZh/ju2lTzdubN3s253nzfH/Xrro/Pzl5cW3b1Y916ODjkZutKCCOHHQQnHNO7dd3\n7uzz/dbmmGN8EHUUBG/f7vMGR3+t6NoVbrvNj1dW+rnKyribdkUF/PWvfnzHDg+0wV/fqxfMng2X\nXbb/5779tqfyfvxx+OlPPcAdPx6uuiqt4pBmTi2zIiKSpub8M0xEZD9mcYNdNBtPfaI8TsnBbnL+\npuTtLVu8FXj+fN+urKz7vdu3rz3YrR74Jud06tChmbYI1yU/P05rXZNeveCOO2p//ahRsHJlvL9n\njxdwFHCfeaZ3s46C3WiJxhT36AHnnuvHOnZsmO8kzYeCWRERSZOCWRFp8ZLzOKUaAEe+/LLqDD7J\nQW9Nx5Ysibdr6w4dKSioGuAm52xK9VhW527Kz/cvEYkykNXmjDN8kZZJU/OIiEiaFMyKiNShoMCT\nUHXpkt7rQvCxwFGgG+VkSk5inLwfLWVl8bkdO+r/nGiWnuQlytuUvK7pWPK6sNCDfpGM0dQ8IiKS\nJgWzIiKNwMwDxMLCuuf3rcvevT48NZUgODq3bZtnit62LR7aGsUIdWnVqu6At75gOPmaNm0UGMsB\nUDdjERFJk4JZEZFmKi8vHnd7oKIW4uTkxcmBbk3raHvr1jjfU3QspDDTd35++sFw+/ZxV/AokXG0\nreA4R6ibsYiIpEnBrIhIC5bcQpxuV+nqokzSqQTG1Y9t3uyJtaJzqXShTv4OyYFutDzyiCdYlhZC\n3YxFRCRNCmZFRCQlyZmku3X7+95r3z4PaJMD3srK+pdotp/KyixPfiX7y8vz1N+tW2f6TkREJEso\nmBURkSYXjdE95JDaZ/qRHHPqqd6ELyIikiL15REREREREZGso2BWREQkh5jZN8yszMyWmdn4Gs5f\nZWYVZrYgsYzJxH2KiIjUR92MRUREcoSZ5QH3A2cD5cBcM5sWQlhS7dInQwjjmvwGRURE0qCWWRER\nkdwxHFgWQlgRQvgSeAK4KMP3JCIickAUzIqIiOSOEmBt0n554lh1F5vZ+2b2tJn1bJpbExERSY+C\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x24e511f34a8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 设置超参数\n",
    "num_epochs = 1000\n",
    "learning_rate = 0.1\n",
    "batch_size = 128\n",
    "eps=1e-7 # 用于防止除以0、log(0)等数学问题\n",
    "\n",
    "# 创建一个层大小依次为[2, 4, 1]的多层感知机\n",
    "# 对于二分类任务，我们用sigmoid作为输出层的激活函数，使其输出在[0,1]之间\n",
    "mlp = MLP(layer_sizes=[2, 4, 1], use_bias=True, out_activation='sigmoid')\n",
    "\n",
    "# 训练过程\n",
    "losses = []\n",
    "test_losses = []\n",
    "test_accs = []\n",
    "for epoch in range(num_epochs):\n",
    "    # 我们实现的MLP支持批量输入，因此采用SGD算法\n",
    "    st = 0\n",
    "    loss = 0.0\n",
    "    while True:\n",
    "        ed = min(st + batch_size, len(x_train))\n",
    "        if st >= ed:\n",
    "            break\n",
    "        # 取出batch\n",
    "        x = x_train[st: ed]\n",
    "        y = y_train[st: ed]\n",
    "        # 计算MLP的预测\n",
    "        y_pred = mlp.forward(x)\n",
    "        # 计算梯度∂J/∂y\n",
    "        grad = (y_pred - y) / (y_pred * (1 - y_pred) + eps)\n",
    "        # 反向传播\n",
    "        mlp.backward(grad)\n",
    "        # 更新参数\n",
    "        mlp.update(learning_rate)\n",
    "        # 计算交叉熵损失\n",
    "        train_loss = np.sum(-y * np.log(y_pred + eps) - (1 - y) * np.log(1 - y_pred + eps))\n",
    "        loss += train_loss\n",
    "        st += batch_size\n",
    "\n",
    "    losses.append(loss / len(x_train))\n",
    "    # 计算测试集上的交叉熵和精度\n",
    "    y_pred = mlp.forward(x_test)\n",
    "    test_loss = np.sum(-y_test * np.log(y_pred + eps) - (1 - y_test) * np.log(1 - y_pred + eps)) / len(x_test)\n",
    "    test_acc = np.sum(np.round(y_pred) == y_test) / len(x_test)\n",
    "    test_losses.append(test_loss)\n",
    "    test_accs.append(test_acc)\n",
    "    \n",
    "print('测试精度：', test_accs[-1])\n",
    "# 将损失变化进行可视化\n",
    "plt.figure(figsize=(16, 6))\n",
    "plt.subplot(121)\n",
    "plt.plot(losses, color='blue', label='train loss')\n",
    "plt.plot(test_losses, color='red', ls='--', label='test loss')\n",
    "plt.xlabel('Step')\n",
    "plt.ylabel('Loss')\n",
    "plt.title('Cross-Entropy Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(122)\n",
    "plt.plot(test_accs, color='red')\n",
    "plt.ylim(top=1.0)\n",
    "plt.xlabel('Step')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.title('Test Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting torch\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/c4/49/9da10fef2c2ba8ff91eeab70a123ca60d082b1012b3aff7825c9b1115852/torch-1.10.2-cp36-cp36m-win_amd64.whl (226.6MB)\n",
      "Collecting dataclasses; python_version < \"3.7\" (from torch)\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/fe/ca/75fac5856ab5cfa51bbbcefa250182e50441074fdc3f803f6e76451fab43/dataclasses-0.8-py3-none-any.whl\n",
      "Collecting typing-extensions (from torch)\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl\n",
      "Installing collected packages: dataclasses, typing-extensions, torch\n",
      "Successfully installed dataclasses-0.8 torch-1.10.2 typing-extensions-4.1.1\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "You are using pip version 9.0.1, however version 24.2 is available.\n",
      "You should consider upgrading via the 'python -m pip install --upgrade pip' command.\n"
     ]
    }
   ],
   "source": [
    "!pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "cell_id": "a11de343daf24ac1b87410184bce8ccb",
    "collapsed": true,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 3017,
    "execution_start": 1672391372327,
    "id": "0C1A50320D34469BA86DFD7BBE27E87E",
    "jupyter": {},
    "notebookId": "6050353b5316950016ec1560",
    "scrolled": false,
    "slideshow": {
     "slide_type": "slide"
    },
    "source_hash": "53ffe62a",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch # PyTorch库\n",
    "import torch.nn as nn # PyTorch中与神经网络相关的工具\n",
    "from torch.nn.init import normal_ # 正态分布初始化\n",
    "\n",
    "torch_activation_dict = {\n",
    "    'identity': lambda x: x,\n",
    "    'sigmoid': torch.sigmoid,\n",
    "    'tanh': torch.tanh,\n",
    "    'relu': torch.relu\n",
    "}\n",
    "\n",
    "# 定义MLP类，基于PyTorch的自定义模块通常都继承nn.Module\n",
    "# 继承后，只需要实现forward函数，进行前向传播\n",
    "# 反向传播与梯度计算均由PyTorch自动完成\n",
    "class MLP_torch(nn.Module):\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        layer_sizes, # 包含每层大小的list\n",
    "        use_bias=True, \n",
    "        activation='relu',\n",
    "        out_activation='identity'\n",
    "    ):\n",
    "        super().__init__() # 初始化父类\n",
    "        self.activation = torch_activation_dict[activation]\n",
    "        self.out_activation = torch_activation_dict[out_activation]\n",
    "        self.layers = nn.ModuleList() # ModuleList以列表方式存储PyTorch模块\n",
    "        num_in = layer_sizes[0]\n",
    "        for num_out in layer_sizes[1:]:\n",
    "            # 创建全连接层\n",
    "            self.layers.append(nn.Linear(num_in, num_out, bias=use_bias))\n",
    "            # 正态分布初始化，采用与前面手动实现时相同的方式\n",
    "            normal_(self.layers[-1].weight, std=1.0)\n",
    "            # 偏置项为全0\n",
    "            self.layers[-1].bias.data.fill_(0.0)\n",
    "            num_in = num_out\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 前向传播\n",
    "        # PyTorch可以自行处理batch_size等维度问题\n",
    "        # 我们只需要让输入依次通过每一层即可\n",
    "        for i in range(len(self.layers) - 1):\n",
    "            x = self.layers[i](x)\n",
    "            x = self.activation(x)\n",
    "        # 输出层\n",
    "        x = self.layers[-1](x)\n",
    "        x = self.out_activation(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "cell_id": "02d475246cf044cc88abc01c2eaeae4a",
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 10685,
    "execution_start": 1672391375357,
    "source_hash": "88df290a",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试精度： 0.9649999737739563\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n7bdfp+NLkqTqV5M9s00/1q9aVWwOSeosevfuvdprUBcuXEi/fv3o1asXTz75JA888MB6n7NPnz70\n69eP++67D4Brr72W/fbbj8bGRmbOnMn48eO5+OKLWbBgAUuWLOGZZ55h55135qyzzmLcuHE8+eST\n651BkiRVr5rsmW0qZtcwAackqWzAgAHsu+++7LTTThx88MEceuihb3r9oIMO4sc//jFjxozhHe94\nB3vvvfcGOe/Pf/5zPvOZz/Dqq6+y9dZb89Of/pSGhgYmTJjAwoULyUzOOOMM+vbty3nnncddd91F\n165dGT16NAcffPAGySBJkqpTZGbRGdbKuHHjcsqUKet1jEcfLU12csstcNRRGyiYJFXQ1KlT2WGH\nHYqO0em19jlGxEOZOa6gSBuFDdE2S5LUpL1ts8OMJUmSJEmdjsWsJEmSJKnTsZiVJEmSJHU6NTkB\nVNeupaXFrCRJkt7kD3+AOXOKTiF1TkceCf37d9jparKYtWdWkiRJbzF9Ohx2WNEppM5r3DiL2Urz\n1jySJEl6i2efLS1vugn22qvYLFJnNHhwh56upotZe2YlqX0WLFjA9ddfz6mnnrpO77/ssss4+eST\n6dWr11te23///bn00ksZN86740haD5kwY8b69VY8/HBpudtuMGLEhsklqWIsZiVJa7RgwQKuvPLK\n9SpmJ0yY0GoxK0kbxJVXwmmnrf9x6upg2LD1P46kirOYlSSt0dlnn80zzzzD2LFjed/73scll1zC\nJZdcwk033cTy5cs56qij+PrXv87SpUs55phjqK+vp6GhgfPOO4+XXnqJ2bNnM378eAYOHMhdd93V\n5nluuOEGLrzwQjKTQw89lO985zs0NDTwyU9+kilTphARnHTSSZxxxhlcfvnl/PjHP6Zbt26MHj2a\nG2+8sQM/EUlV57HHoE8fuPzy9TvOqFHQo8eGySSpoixmJakz2n//t2475hg49VR49VU45JC3vn7i\niaXHvHnwoQ+9+bW7717t6S666CIee+wxHnnkEQDuuOMOnn76aR588EEyk8MPP5x7772XuXPnsuWW\nW3LbbbcBsHDhQvr06cP3vvc97rrrLgYOHNjmOWbPns1ZZ53FQw89RL9+/TjwwAP53e9+x/Dhw5k1\naxaPPfYYUOolbsr07LPP0qNHj9e3SaphM2aUCtGPfazoJJI6iPeZlSSttTvuuIM77riDXXfdld12\n240nn3ySp59+mp133pk777yTs846i/vuu48+ffq0+5iTJ09m//33Z9CgQXTr1o0TTjiBe++9l623\n3prp06dz+umn86c//YnNN98cgDFjxnDCCSdw3XXX0a1bTf42K9WGZctKkzFFwPDhbT/+/OfSUlLN\nqMnW3/vMSur0VteT2qvX6l8fOHCNPbFrkpmcc845nHLKKW957aGHHmLSpEmcc845HHjggZx//vnt\nPmZr+vXrx6OPPsrtt9/OFVdcwU033cQ111zDbbfdxr333svEiRP5xje+weOPP25RK22MnnkGHnyw\n9HzYMBg9uu197ZWVakpNtvoR0KWLxawktVfv3r1ZvHjx6+vvf//7Oe+88zjhhBPYbLPNmDVrFnV1\ndaxatYr+/fszYcIENttsM372s5+96f2rG2a811578YUvfIF58+bRr18/brjhBk4//XTmzZtH9+7d\nOfroo9lmm2048cQTaWxsZObMmYwfP553vetdXH/99SxZsoS+fftW+qOQ1NFmznzj+Re+AMcdV1wW\nSVWlJotZKA019j6zktQ+AwYMYN9992WnnXbi4IMP5pJLLmHq1Km8853vBGCzzTbjuuuuY9q0aXz5\ny1+mS5cu1NXV8aMf/QiAk08+mYMPPpghQ4a0OQHUkCFD+Pa3v8348ePJTA455BCOOOIIHn30UT7x\niU/Q2NgIwLe//W0aGhqYMGECCxcuJDM544wzLGRV3Roa4P/7/+CllzrmfD16wDe/CUOGrP17X3kF\nzjqrdP19NWi69ytAz57F5ZBUdaKtYV3Vaty4cTllypT1Ps6mm5bmSbnkkg0QSpIqbOrUqeywww5F\nx+j0WvscI+KhzPQmt+thQ7XNG7XHH4eddoLBg0tfQiqpoQGeew6uugo+/em1f//NN8OHP1y6z2pd\n3QaPt05eew223homTYLevYtOI6nC2ts2V7RnNiIOAn4AdAV+kpkXtXj9+8D48movYIvM7JCf1rt1\nc5ixJEnqIE1DZW++Gfbdt7LnWrWq1DPbfHju2mh638MPQ//+Gy6XJG1gFStmI6IrcAXwPqAemBwR\nEzPziaZ9MvOMZvufDuxaqTwtWcxKkqS18te/liYjWhf/+Edp2RGz7XbrVhpefPfdcPXVa//+228v\nTSTXr98GjyZJG1Ile2b3BKZl5nSAiLgROAJ4oo39jwf+q4J53sRiVpIktduKFfD+96/fl4cBA9bt\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x24e5027b7b8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 设置超参数\n",
    "num_epochs = 1000\n",
    "learning_rate = 0.1\n",
    "batch_size = 128\n",
    "eps = 1e-7\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 初始化MLP模型\n",
    "mlp = MLP_torch(layer_sizes=[2, 4, 1], use_bias=True, out_activation='sigmoid')\n",
    "\n",
    "# 定义SGD优化器\n",
    "opt = torch.optim.SGD(mlp.parameters(), lr=learning_rate)\n",
    "\n",
    "# 训练过程\n",
    "losses = []\n",
    "test_losses = []\n",
    "test_accs = []\n",
    "for epoch in range(num_epochs):\n",
    "    st = 0\n",
    "    loss = []\n",
    "    while True:\n",
    "        ed = min(st + batch_size, len(x_train))\n",
    "        if st >= ed:\n",
    "            break\n",
    "        # 取出batch，转为张量\n",
    "        x = torch.tensor(x_train[st: ed], dtype=torch.float32)\n",
    "        y = torch.tensor(y_train[st: ed], dtype=torch.float32).reshape(-1, 1)\n",
    "        # 计算MLP的预测\n",
    "        # 调用模型时，PyTorch会自动调用模型的forward方法\n",
    "        # y_pred的维度为(batch_size, layer_sizes[-1])\n",
    "        y_pred = mlp(x)\n",
    "        # 计算交叉熵损失\n",
    "        train_loss = torch.mean(-y * torch.log(y_pred + eps) - (1 - y) * torch.log(1 - y_pred + eps))\n",
    "        # 清空梯度\n",
    "        opt.zero_grad()\n",
    "        # 反向传播\n",
    "        train_loss.backward()\n",
    "        # 更新参数\n",
    "        opt.step()\n",
    "\n",
    "        # 记录累加损失，需要先将损失从张量转为numpy格式\n",
    "        loss.append(train_loss.detach().numpy())\n",
    "        st += batch_size\n",
    "\n",
    "    losses.append(np.mean(loss))\n",
    "    # 计算测试集上的交叉熵\n",
    "    # 在不需要梯度的部分，可以用torch.inference_mode()加速计算\n",
    "    with torch.inference_mode():\n",
    "        x = torch.tensor(x_test, dtype=torch.float32)\n",
    "        y = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)\n",
    "        y_pred = mlp(x)\n",
    "        test_loss = torch.sum(-y * torch.log(y_pred + eps) - (1 - y) * torch.log(1 - y_pred + eps)) / len(x_test)\n",
    "        test_acc = torch.sum(torch.round(y_pred) == y) / len(x_test)\n",
    "        test_losses.append(test_loss.detach().numpy())\n",
    "        test_accs.append(test_acc.detach().numpy())\n",
    "\n",
    "print('测试精度：', test_accs[-1])\n",
    "# 将损失变化进行可视化\n",
    "plt.figure(figsize=(16, 6))\n",
    "plt.subplot(121)\n",
    "plt.plot(losses, color='blue', label='train loss')\n",
    "plt.plot(test_losses, color='red', ls='--', label='test loss')\n",
    "plt.xlabel('Step')\n",
    "plt.ylabel('Loss')\n",
    "plt.title('Cross-Entropy Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(122)\n",
    "plt.plot(test_accs, color='red')\n",
    "plt.ylim(top=1.0)\n",
    "plt.xlabel('Step')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.title('Test Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "deepnote": {},
  "deepnote_execution_queue": [],
  "deepnote_notebook_id": "3b94328ab2964431b83f6d3de7ee135e",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
